package cn.shentianlan.service.impl;

import cn.shentianlan.criteria.CourseSchedulingCriteria;
import cn.shentianlan.domain.dto.ClassroomSchedulingDTO;
import cn.shentianlan.domain.dto.CourseSchedulingBatchSaveDTO;
import cn.shentianlan.domain.dto.CourseSchedulingListVO;
import cn.shentianlan.domain.entity.ClassroomScheduling;
import cn.shentianlan.domain.entity.CourseScheduling;
import cn.shentianlan.mapper.CourseSchedulingMapper;
import cn.shentianlan.service.ClassroomSchedulingService;
import cn.shentianlan.service.CourseSchedulingService;
import cn.shentianlan.utils.BaseUtils;
import cn.shentianlan.utils.DataUtils;
import cn.shentianlan.utils.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * @Author 陈俊源
 * @Description TODO
 * @Date 2021/5/1
 * @Version 1.0
 */
@Service
public class CourseSchedulingServiceImpl implements CourseSchedulingService {

    @Autowired
    private CourseSchedulingMapper courseSchedulingMapper;

    @Autowired
    private ClassroomSchedulingService classroomSchedulingService;

    /**
     * 批量保存排课信息
     * @param courseSchedulingBatchSaveDTO
     * @return
     */
    @Override
    public Result batchSaveCourseScheduling(CourseSchedulingBatchSaveDTO courseSchedulingBatchSaveDTO) {
        List<Integer> weekList = courseSchedulingBatchSaveDTO.getWeekList();
        LocalDate startDate = courseSchedulingBatchSaveDTO.getStartDate();
        LocalDate endDate = courseSchedulingBatchSaveDTO.getEndDate();
        List<LocalDate> dateList = new ArrayList<>();
        while (startDate.compareTo(endDate) <= 0) {
            if (weekList.contains(startDate.getDayOfWeek().getValue())) {
                dateList.add(startDate);
            }
            startDate = startDate.plusDays(1);
        }
        Map<String,Object> args = new HashMap<String,Object>();
        args.put("classroomId",courseSchedulingBatchSaveDTO.getClassroomId());
        args.put("dateList",dateList);
        args.put("teacherId",courseSchedulingBatchSaveDTO.getTeacherId());
        List<CourseSchedulingListVO> courseSchedulingListVOListByClassroomId = courseSchedulingMapper.selectCourseSchedulingListByClassroomId(args);
        Set<String> errorSet = new HashSet<>();
        //判断课室是否出现冲突情况
        for (CourseSchedulingListVO vo : courseSchedulingListVOListByClassroomId) {
            if (isTimeConflict(courseSchedulingBatchSaveDTO.getAttendTime(), courseSchedulingBatchSaveDTO.getFinishTime(), vo.getAttendTime(), vo.getFinishTime())) {
                errorSet.add(String.format("%s %s-%s %s %s %s", vo.getDate(), vo.getAttendTime(), vo.getFinishTime(), vo.getClassroomName(), vo.getCourseName(), vo.getTeacherName()));
            }
        }
        //判断上课教师是否出现冲突情况
        List<CourseSchedulingListVO> courseSchedulingListVOListByTeacherId = courseSchedulingMapper.selectCourseSchedulingListByTeacherId(args);
        for (CourseSchedulingListVO vo : courseSchedulingListVOListByTeacherId) {
            if (isTimeConflict(courseSchedulingBatchSaveDTO.getAttendTime(), courseSchedulingBatchSaveDTO.getFinishTime(), vo.getAttendTime(), vo.getFinishTime())) {
                errorSet.add(String.format("%s %s-%s %s %s %s", vo.getDate(), vo.getAttendTime(), vo.getFinishTime(), vo.getClassroomName(), vo.getCourseName(), vo.getTeacherName()));
            }
        }
        if (errorSet.size() > 0) {
            return Result.fail(errorSet,"检测到排课信息冲突");
        }
        for (LocalDate date : dateList) {
            CourseScheduling courseScheduling = BaseUtils.copyProperties(courseSchedulingBatchSaveDTO, CourseScheduling.class);
            courseScheduling.setDate(date);
            courseScheduling.setId(DataUtils.assignId());
            courseSchedulingMapper.save(courseScheduling);

            ClassroomSchedulingDTO classroomSchedulingDTO = bulidClassroomSchedulingDTO(null, courseScheduling.getClassroomId(), courseScheduling.getTeacherId(), courseScheduling.getDate(), courseScheduling.getAttendTime(), courseScheduling.getFinishTime());
            classroomSchedulingService.saveOrUpdate(classroomSchedulingDTO);
        }
        return Result.success();
    }

    /**
     * 根据相关条件查询出排课信息
     * @param courseSchedulingCriteria
     * @return
     */
    @Override
    public Result getCourseSchedulingList(CourseSchedulingCriteria courseSchedulingCriteria) {
        List<CourseSchedulingListVO> courseSchedulingListVOListByCriteria = courseSchedulingMapper.selectCourseSchedulingList(courseSchedulingCriteria);
        return Result.success(courseSchedulingListVOListByCriteria);
    }

    /**
     * 根据相关条件查询出课程数量
     * @param courseSchedulingCriteria
     * @return
     */
    @Override
    public Result getCourseSchedulingCourseCount(CourseSchedulingCriteria courseSchedulingCriteria) {
        Map<String, Integer> map = new HashMap<>();
        List<Map<String,Object>> list =  courseSchedulingMapper.selectCourseSchedulingCourseCount(courseSchedulingCriteria);
        if (list != null && list.size() > 0) {
            list.forEach(item -> map.put(String.valueOf(item.get("date")), item.get("count") != null ? Integer.parseInt(String.valueOf(item.get("count"))) : 0));
        }
        return Result.success(map);
    }

    /**
     * 根据id获取该节课对应的排课信息
     * @param id
     * @return
     */
    @Override
    public Result getCourseSchedulingById(Long id) {
        if (id==null){
            return Result.fail("id为空，无法获取相应信息");
        }
        CourseSchedulingListVO courseSchedulingListVO = courseSchedulingMapper.getCourseSchedulingById(id);
        return Result.success(courseSchedulingListVO);
    }

    /**
     * 保存或更新排课信息
     * @param courseScheduling
     * @return
     */
    @Transactional
    @Override
    public Result saveOrUpdateCourseScheduling(CourseScheduling courseScheduling) {
        //检测排课是否出现冲突
        Result result = check(courseScheduling.getId(),courseScheduling.getClassroomId(),courseScheduling.getTeacherId(),
                courseScheduling.getDate(),courseScheduling.getAttendTime(),courseScheduling.getFinishTime());
        if (!Result.isSuccess(result)){
            return result;
        }
        ClassroomSchedulingDTO classroomSchedulingDTO = bulidClassroomSchedulingDTO(null, courseScheduling.getClassroomId(),courseScheduling.getTeacherId(),courseScheduling.getDate(), courseScheduling.getAttendTime(), courseScheduling.getFinishTime());
        if (courseScheduling.getId()==null){
            courseScheduling.setId(DataUtils.assignId());
            courseSchedulingMapper.save(courseScheduling);
        }else{
            LocalDateTime startTime = LocalDateTime.of(courseScheduling.getDate(),courseScheduling.getAttendTime());
            LocalDateTime endTime = LocalDateTime.of(courseScheduling.getDate(),courseScheduling.getFinishTime());
            ClassroomScheduling classroomScheduling = classroomSchedulingService.getClassrooomSchedulingByClassroomIdAndStartTimeAndEndTime(courseScheduling.getClassroomId(),startTime,endTime);
            if (classroomScheduling != null){
                classroomSchedulingDTO.setId(classroomScheduling.getId());
            }
            courseSchedulingMapper.update(courseScheduling);
        }
        classroomSchedulingService.saveOrUpdate(classroomSchedulingDTO);
        return Result.success(courseScheduling.getId());
    }

    /**
     * 根据id删除排课信息
     * @param id
     * @return
     */
    @Override
    public Result deleteCourseSchedulingById(Long id) {
        if(id==null){
            return Result.fail("id为空，无法执行删除操作");
        }
        CourseSchedulingListVO courseSchedulingById = courseSchedulingMapper.getCourseSchedulingById(id);
        if (courseSchedulingById==null){
            return Result.fail("无法根据id获取课程安排信息");
        }
        //删除相应课室安排信息
        LocalDateTime startTime = LocalDateTime.of(courseSchedulingById.getDate(),courseSchedulingById.getAttendTime());
        LocalDateTime endTime = LocalDateTime.of(courseSchedulingById.getDate(),courseSchedulingById.getFinishTime());
        ClassroomScheduling classroomScheduling = classroomSchedulingService.getClassrooomSchedulingByClassroomIdAndStartTimeAndEndTime(courseSchedulingById.getClassroomId(),startTime,endTime);
        courseSchedulingMapper.deleteCourseSchedulingById(id);
        if (classroomScheduling!=null){
            classroomSchedulingService.deleteCLassroomSchedulingById(classroomScheduling.getId());
        }
        return Result.success(id);
    }

    private Integer calcMinute(LocalTime time) {
        return time.getHour() * 60 + time.getMinute();
    }

    private boolean isBetween(LocalTime time, LocalTime destTime1, LocalTime destTime2) {
        Integer minute = calcMinute(time);
        return calcMinute(destTime1) < minute && minute < calcMinute(destTime2);
    }

    /**
     * 新增时间范围跨度较小，在现有时间范围内
     * 新增时间是否在已有课程的时间段，在则冲突
     * 新增时间范围跨度较大大，包含现有时间段
     * 现有时间是否在新增时间范围内，在则冲突
     * 时间完全相等
     */
    private boolean isTimeConflict(LocalTime attendTime1, LocalTime finishTime1, LocalTime attendTime2, LocalTime finishTime2) {
        return isBetween(attendTime1, attendTime2, finishTime2)
                || isBetween(finishTime1, attendTime2, finishTime2)
                || isBetween(attendTime2, attendTime1, finishTime1)
                || isBetween(finishTime2, attendTime1, finishTime1)
                || (attendTime1.equals(attendTime2) && finishTime1.equals(finishTime2));
    }


    private Result check(Long id, Long classroomId, Long teacherId, LocalDate date, LocalTime attendTime, LocalTime finishTime) {
        Map<String,Object> args = new HashMap<String, Object>();
        args.put("id",id);
        args.put("classroomId",classroomId);
        args.put("date",date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        args.put("attendTime",attendTime);
        args.put("finishTime",finishTime);
        List<CourseSchedulingListVO> courseSchedulingListVOListByClassroomId = courseSchedulingMapper.selectCourseSchedulingListByCheck(args);
        for (CourseSchedulingListVO vo : courseSchedulingListVOListByClassroomId) {
            /**
             * 同一教室不能同时上多节课
             */
            if (isTimeConflict(attendTime, finishTime, vo.getAttendTime(), vo.getFinishTime())) {
                return Result.fail(String.format("教室时间冲突，冲突信息【%s %s-%s %s %s %s】",
                        vo.getDate(), vo.getAttendTime(), vo.getFinishTime(), vo.getClassroomName(), vo.getCourseName(), vo.getTeacherName()));
            }
        }
        args.remove("classroomId");
        args.put("teacherId",teacherId);
        List<CourseSchedulingListVO> courseSchedulingListVOListByTeacherId = courseSchedulingMapper.selectCourseSchedulingListByCheck(args);
        for (CourseSchedulingListVO vo : courseSchedulingListVOListByTeacherId) {
            /**
             * 同一老师不能同时上多节课
             */
            if (isTimeConflict(attendTime, finishTime, vo.getAttendTime(), vo.getFinishTime())) {
                return Result.fail(String.format("老师时间冲突，冲突信息【%s %s-%s %s %s %s】",
                        vo.getDate(), vo.getAttendTime(), vo.getFinishTime(), vo.getClassroomName(), vo.getCourseName(), vo.getTeacherName()));
            }
        }
        return Result.success();
    }

    /**
     * 构建课室安排信息DTO
     * @param id
     * @param classroomId
     * @param date
     * @param attend
     * @param finshedTime
     * @return
     */
    private ClassroomSchedulingDTO bulidClassroomSchedulingDTO(Long id, Long classroomId,Long userId,LocalDate date,LocalTime attend,LocalTime finshedTime){
        ClassroomSchedulingDTO classroomSchedulingDTO = new ClassroomSchedulingDTO();
        classroomSchedulingDTO.setId(id);
        classroomSchedulingDTO.setUserId(userId);
        classroomSchedulingDTO.setClassroomId(classroomId);
        LocalDateTime startTime = LocalDateTime.of(date,attend);
        LocalDateTime endTime = LocalDateTime.of(date,finshedTime);
        classroomSchedulingDTO.setStartTime(startTime);
        classroomSchedulingDTO.setEndTime(endTime);
        return classroomSchedulingDTO;
    }
}
